/* Copyright (c) 2015,2016 Lucky Byte, Inc.
 */
package com.lucky_byte.pay.jar;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.math.BigDecimal;
import java.sql.Connection;
import java.sql.Date;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.sql.Time;
import java.sql.Timestamp;
import java.sql.Types;
import java.util.ArrayList;
import java.util.List;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.postgresql.copy.CopyManager;
import org.postgresql.core.BaseConnection;

/**
 * 数据表操作接口
 *
 * 这是一个通用接口，每个数据表由多个记录构成(JdbcRecord)，每个记录由多个字段组成.
 */
public class JdbcTable
{
	private static final Logger logger = LogManager.getLogger();

	private final String table_name;
	private final List<JdbcRecord> records;

	public JdbcTable(String table_name) {
		this.table_name = table_name;
		records = new ArrayList<>();
	}

	/**
	 * 获取表名
	 *
	 * @return 数据表名
	 */
	public String getTableName() {
		return table_name;
	}

	/**
	 * 获取表中记录条数
	 *
	 * <p>
	 * 注意 JdbcTable 用于存放查询结果，因此其存储的记录数只是实际数据库中的一小部分.
	 * </p>
	 *
	 * @return 保存的记录数
	 */
	public int getRecordCount() {
		return records.size();
	}

	/**
	 * 通过索引获取记录
	 *
	 * @param index 索引
	 * @return DRecord 对象，或者 null
	 */
	public JdbcRecord getRecord(int index) {
		return records.get(index);
	}

	/**
	 * 查找数据表中所有的记录
	 *
	 * @param table_name 数据表名
	 * @return DTable 对象
	 * @throws SQLException SQL 异常
	 */
	static public JdbcTable listAll(String table_name)
			throws SQLException {
		return listBy(table_name, null, null);
	}

	/**
	 * 不抛出异常版本
	 */
	static public JdbcTable listAll_NE(String table_name) {
		try {
			return listAll(table_name);
		} catch (SQLException e) {
			logger.error("查询数据表[{}]错误[{}].",
					table_name, e.getMessage());
			return null;
		}
	}

	/**
	 * 根据条件查询数据库记录
	 *
	 * <p>
	 * 这个函数可以执行几乎所有的单表 sql 查询，普通查询:
	 * </p>
	 * <pre>
	 *    select * from table where 1=1 and name = 'joe'
	 * </pre>
	 * <p>
	 * 其中<b>"name = 'joe'"</b> 是 #sql_conds 参数，
	 * 建议最好是将参数放在 #params 列表中，这样可以避免一些 sql 语法上的错误，例如：
	 * </p>
	 * <pre>
	 *      String sql_conds = "name = ?";
	 *      List&lt;Object&gt; params = new ArrayList&lt;&gt;();
	 *      params.add("joe");
	 *      DTable.listBy("table", sql_conds, params);
	 * </pre>
	 * <pre>
	 * Like 查询：
	 *     select * from table where 1=1 and name like '%joe'
	 * </pre>
	 * <pre>
	 * Between 查询：
	 *     select * from table where 1=1 and age between 10 and 20
	 * </pre>
	 *
	 * @param table_name 表名
	 * @param sql_clause sql 条件语句
	 * @param sql_params 参数
	 * @return JdbcTable 对象
	 * @throws SQLException SQL 异常
	 */
	static public JdbcTable listBy(String table_name, String sql_clause,
			List<Object> sql_params) throws SQLException {
		StringBuilder sql = new StringBuilder();
		sql.append("select * from ");
		sql.append(table_name);
		if (sql_clause != null) {
			sql.append(" where ");
			sql.append(sql_clause);
		}
		return query(sql.toString(), sql_params);
	}

	/**
	 * 不抛异常版本
	 */
	static public JdbcTable listBy_NE(String table_name, String sql_clause,
			List<Object> sql_params) {
		try {
			return listBy(table_name, sql_clause, sql_params);
		} catch (SQLException e) {
			logger.error("查询数据表[{}]错误[{}].",
					table_name, e.getMessage());
			return null;
		}
	}

	/**
	 * 上面函数的一个简化版本，只接受一个参数.
	 *
	 * @param table_name 表名
	 * @param sql_clause sql 条件语句
	 * @param param 参数
	 * @return JdbcTable 对象
	 * @throws SQLException SQL 异常
	 */
	static public JdbcTable listBy1(String table_name, String sql_clause,
			Object param) throws SQLException {
		List<Object> params = new ArrayList<>();
		params.add(param);
		return listBy(table_name, sql_clause, params);
	}

	/**
	 * 不抛异常版本
	 */
	static public JdbcTable listBy1_NE(String table_name,
			String sql_clause, Object param) {
		try {
			return listBy1(table_name, sql_clause, param);
		} catch (SQLException e) {
			logger.error("查询数据表[{}]错误[{}].",
					table_name, e.getMessage());
			return null;
		}
	}

	/**
	 * 查询表记录数
	 *
	 * @param table_name 表名
	 * @return 记录数
	 * @throws SQLException SQL 异常
	 */
	static public long getCount(String table_name) throws SQLException {
		StringBuilder sql = new StringBuilder();
		sql.append("select count(*) as count from ").append(table_name);
		JdbcTable table = query(sql.toString(), null);
		if (table == null) {
			return -1;
		}
		JdbcRecord record = table.getRecord(0);
		return record.getLong("count");
	}

	/**
	 * 不抛异常版本
	 */
	static public long getCount_NE(String table_name) {
		try {
			return getCount(table_name);
		} catch (SQLException e) {
			logger.error("查询数据表[{}]错误[{}].", table_name, e.getMessage());
			return -1;
		}
	}

	/**
	 * 查询表记录数
	 *
	 * @param table_name 表名
	 * @param sql SQL 语句
	 * @param sql_params SQL 参数
	 * @return 记录数
	 * @throws SQLException SQL 异常
	 */
	static public long getCount(String table_name, String sql_clause,
			List<Object> sql_params) throws SQLException {
		StringBuilder sql = new StringBuilder();
		sql.append("select count(*) as count from ").append(table_name);
		if (sql_clause != null) {
			sql.append(" where ");
			sql.append(sql_clause);
		}
		JdbcTable table = query(sql.toString(), sql_params);
		if (table == null) {
			return -1;
		}
		JdbcRecord record = table.getRecord(0);
		return record.getLong("count");
	}

	/**
	 * 不抛异常版本
	 */
	static public long getCount_NE(String table_name,
			String sql_clause, List<Object> sql_params) {
		try {
			return getCount(table_name, sql_clause, sql_params);
		} catch (SQLException e) {
			logger.error("查询数据表[{}]错误[{}].", table_name, e.getMessage());
			return -1;
		}
	}

	/**
	 * 查询表记录数
	 *
	 * @param sql
	 *            SQL 语句
	 * @param sql_params
	 *            SQL 参数
	 * @return 记录数
	 * @throws SQLException
	 *             SQL 异常
	 */
	static public long getCount(String sql, List<Object> sql_params)
			throws SQLException {
		JdbcTable table = query(sql, sql_params);
		if (table == null) {
			return -1;
		}
		JdbcRecord record = table.getRecord(0);
		return record.getLong("count");
	}

	/**
	 * 不抛异常版本
	 */
	static public long getCount_NE(String sql, List<Object> sql_params) {
		try {
			return getCount(sql, sql_params);
		} catch (SQLException e) {
			logger.error("执行SQL[{}]错误[{}].", sql, e.getMessage());
			return -1;
		}
	}

	/**
	 * 执行 sql 查询
	 *
	 * @param sql
	 *            SQL 语句
	 * @param sql_params
	 *            SQL 参数
	 * @return JdbcTable 对象
	 * @throws SQLException
	 *             SQL 异常
	 */
	static public JdbcTable query(String sql, List<Object> sql_params)
			throws SQLException {
		final JdbcTable table = new JdbcTable("Unset");
		Jdbc.query(sql, sql_params, new Jdbc.QueryListener() {
			@Override
			public void onQuery(ResultSet rset) throws SQLException {
				while (rset.next()) {
					ResultSetMetaData metadata = rset.getMetaData();
					JdbcRecord record = new JdbcRecord();
					for (int n = 1; n <= metadata.getColumnCount(); n++) {
						Object object = rset.getObject(n);
						if (!rset.wasNull()) {
							String name = metadata.getColumnLabel(n);
//							logger.trace("添加查询字段[{}], 类型[{}]，值[{}].",
//									name, object.getClass().getName(), object.toString());
							record.setField(name, object);
						}
					}
					table.records.add(record);
				}
			}
		});
		return table;
	}

	/**
	 * 执行 sql 查询
	 *
	 * @param sql
	 *            SQL 语句
	 * @param sql_params
	 *            SQL 参数
	 * @return JdbcTable 对象
	 * @throws SQLException
	 *             SQL 异常
	 */
	static public JdbcTable query2(String sql, Object... sql_params) throws SQLException {
		List<Object> list = new ArrayList<>();
		for (Object obj : sql_params) {
			list.add(obj);
		}
		return query(sql, list);
	}

	/**
	 * 不抛异常版本
	 */
	static public JdbcTable query_NE(String sql, List<Object> sql_params) {
		try {
			return query(sql, sql_params);
		} catch (SQLException e) {
			logger.error("执行SQL[{}]错误[{}].", sql, e.getMessage());
			return null;
		}
	}

	/**
	 * 删除数据, 请小心使用, 不要把数据表清空了
	 *
	 * @param table_name
	 *            表名
	 * @param sql_clause
	 *            条件语句
	 * @param params
	 *            参数
	 * @throws SQLException
	 *             SQL 异常
	 */
	static public void delete(String table_name, String sql_clause,
			List<Object> params) throws SQLException {
		StringBuilder sql = new StringBuilder();

		sql.append("delete from ");
		sql.append(table_name);

		if (sql_clause != null) {
			sql.append(" where ");
			sql.append(sql_clause);
		}
		Jdbc.update(sql.toString(), params);
	}

	/**
	 * 不抛异常版本
	 */
	static public void delete_NE(String table_name,
			String sql_clause, List<Object> params) {
		try {
			delete(table_name, sql_clause, params);
		} catch (SQLException e) {
			logger.error("删除表[{}]中数据错误[{}].", table_name, e.getMessage());
		}
	}

	/**
	 * 删除数据, 等价于 delete(table_name, sql_clause, null);
	 *
	 * @param table_name
	 *            表名
	 * @param sql_clause
	 *            条件语句
	 * @throws SQLException
	 *             SQL 异常
	 */
	static public void delete(String table_name, String sql_clause)
			throws SQLException {
		delete(table_name, sql_clause, null);
	}

	/**
	 * 不抛异常版本
	 */
	static public void delete_NE(String table_name, String sql_clause) {
		try {
			delete(table_name, sql_clause);
		} catch (SQLException e) {
			logger.error("删除表[{}]中数据错误[{}].", table_name, e.getMessage());
		}
	}

	/**
	 * 准备插入语句
	 *
	 * @param connection
	 *            数据库连接
	 * @param table_name
	 *            数据表名称
	 * @param record
	 *            数据表记录
	 * @return PreparedStatement 对象, 或 null
	 * @throws SQLException
	 *             SQL 异常
	 */
	static private PreparedStatement prepareInsertStatement(
			Connection connection, String table_name, JdbcRecord record)
			throws SQLException {
		StringBuilder sql = new StringBuilder();
		List<Object> params = new ArrayList<>();

		sql.append("insert into ");
		sql.append(table_name);

		sql.append("(");
		for (String field : record.FieldSet()) {
			sql.append(field);
			sql.append(",");
			params.add(record.getField(field));
		}
		sql.setLength(sql.length() - 1);
		sql.append(") values (");
		for (int i = 0; i < params.size(); i++) {
			sql.append("?,");
		}
		sql.setLength(sql.length() - 1);
		sql.append(");");

		PreparedStatement statement =
				connection.prepareStatement(sql.toString());
		for (int i = 0; i < params.size(); i++) {
			statement.setObject(i + 1, params.get(i));
		}
		logger.trace("开始执行SQL[{}].", statement.toString());

		statement.setQueryTimeout(30);
		return statement;
	}

	/**
	 * 插入一条记录
	 *
	 * <p>
	 * 这个函数假设 JdbcRecord 对象中保存的数据类型与数据库中的数据类型是一致的， 否则数据库将报告类型不匹配错误。
	 * </p>
	 * <p>
	 * 如果希望进行类型转换，可以使用 insert_updatable() 函数， 它会尝试将字符串参数转换为与数据表中的字段类型一致后执行插入或更新。
	 * </p>
	 *
	 * @param table_name
	 *            数据表名称
	 * @param record
	 *            数据表记录
	 * @throws SQLException
	 *             SQL 异常
	 */
	static public void insert(String table_name, JdbcRecord record)
			throws SQLException {
		long start = System.currentTimeMillis();

		try (Connection connection = Jdbc.getConnection()) {
			connection.setAutoCommit(true);
			PreparedStatement statement = null;
			try {
				statement = prepareInsertStatement(
						connection, table_name, record);
				statement.executeUpdate();
				Jdbc.logElapsed(start, statement);
				statement.close();
			} catch (SQLException e) {
				if (statement != null) {
					statement.close();
				}
				throw e;
			} finally {
				connection.close();
			}
		}
	}

	/**
	 * 不抛异常版本
	 */
	static public void insert_NE(String table_name, JdbcRecord record) {
		try {
			insert(table_name, record);
		} catch (SQLException e) {
			logger.error("插入数据到表[{}]数据错误[{}].", table_name, e.getMessage());
		}
	}

	/**
	 * 插入一条或多条记录，所有操作在一个事务中进行，要么全部插入，要么一条也不插入。
	 *
	 * <p>
	 * 这个函数假设 JdbcRecord 对象中保存的数据类型与数据库中的数据类型是一致的，
	 * 否则数据库将报告类型不匹配错误.
	 * </p>
	 * <p>
	 * 如果希望进行类型转换，可以使用 insert_updatable() 函数，
	 * 它会尝试将字符串参数转换为与数据表中的字段类型一致后执行插入或更新。
	 * </p>
	 *
	 * @param table_name
	 *            数据表名称
	 * @param records
	 *            数据表记录
	 * @throws SQLException
	 *             SQL 异常
	 */
	static public void insert(String table_name, List<JdbcRecord> records)
			throws SQLException {
		long start = System.currentTimeMillis();

		try (Connection connection = Jdbc.getConnection()) {
			connection.setAutoCommit(false);

			for (JdbcRecord record : records) {
				PreparedStatement statement = null;
				try {
					statement = prepareInsertStatement(
							connection, table_name, record);
					statement.executeUpdate();
					statement.close();
				} catch (SQLException e) {
					if (statement != null) {
						statement.close();
					}
					connection.rollback();
					connection.close();
					throw e;
				}
			}
			connection.commit();
			connection.close();
			Jdbc.logElapsed(start, "批量插入表[" + table_name + "]");
		}
	}

	/**
	 * 不抛异常版本
	 */
	static public void insert_NE(String table_name, List<JdbcRecord> records) {
		try {
			insert(table_name, records);
		} catch (SQLException e) {
			logger.error("插入数据到表[{}]数据错误[{}].", table_name, e.getMessage());
		}
	}

	/**
	 * 组织查询语句
	 *
	 * @param connection
	 *            数据库连接
	 * @param table_name
	 *            数据表名称
	 * @param record
	 *            数据表记录
	 * @param cond_keys
	 *            条件字段
	 * @return PreparedStatement 对象, 或 null
	 * @throws SQLException
	 *             SQL 异常
	 */
	static private PreparedStatement prepareQueryStatement(
			Connection connection, String table_name, JdbcRecord record,
			List<String> cond_keys) throws SQLException {
		StringBuilder sql = new StringBuilder();

		sql.append("select * from ");
		sql.append(table_name);
		sql.append(" where 1=1");

		for (String key : cond_keys) {
			sql.append(" and ");
			sql.append(key);
			sql.append(" = ?");
		}
		PreparedStatement statement = connection.prepareStatement(
				sql.toString(), ResultSet.TYPE_FORWARD_ONLY,
				ResultSet.CONCUR_UPDATABLE);
		for (int i = 0; i < cond_keys.size(); i++) {
			statement.setObject(i + 1, record.getField(cond_keys.get(i)));
		}
		logger.trace("开始执行SQL[{}].", statement.toString());

		statement.setQueryTimeout(30);
		return statement;
	}

	/**
	 * 将字符串转换为 SQL 对象类型
	 */
	private static Object stringToJdbcObject(int type, String value) {
		try {
			switch (type) {
			case Types.VARCHAR:
				return value;
			case Types.INTEGER:
			case Types.TINYINT:
			case Types.SMALLINT:
				return Integer.parseInt(value);
			case Types.BIGINT:
			case Types.NUMERIC:
			case Types.DECIMAL:
				return new BigDecimal(value);
			case Types.DOUBLE:
			case Types.FLOAT:
			case Types.REAL:
				return Double.parseDouble(value);
			case Types.BOOLEAN:
			case Types.BIT:
				return Boolean.parseBoolean(value);
			case Types.DATE:
				return Date.valueOf(value);
			case Types.TIME:
			case Types.TIME_WITH_TIMEZONE:
				return Time.valueOf(value);
			case Types.TIMESTAMP:
			case Types.TIMESTAMP_WITH_TIMEZONE:
				return Timestamp.valueOf(value);
			case Types.OTHER:
				return value;
			default:
				logger.info("未作特殊处理的JDBC类型 {}", type);
				return value;
			}
		} catch (Exception e) {
			logger.error("尝试转换字符串到SQL数据类型[{}]出错[{}][{}]",
					type, e.getMessage(), e.getClass().getSimpleName());
			return value;
		}
	}

	/**
	 * 将字符串转换为 SQL 对象类型
	 */
	private static Object stringToJdbcObject(ResultSetMetaData metadata,
			String field, String string)
			throws SQLException {
		for (int n = 1; n <= metadata.getColumnCount(); n++) {
			String name = metadata.getColumnLabel(n);
			if (!field.equals(name)) {
				continue;
			}
			int type = metadata.getColumnType(n);
			return stringToJdbcObject(type, (String) string);
		}
		return string;
	}

	/**
	 * 执行插入或更新操作，根据条件对数据库进行查询，如果记录已经存在，则更新之， 否则插入新记录。
	 * <p>
	 * 这个函数支持多个唯一索引。
	 * </p>
	 *
	 * @param table_name
	 *            数据表名称
	 * @param record
	 *            数据表记录
	 * @param cond_keys
	 *            唯一性条件字段组合
	 * @throws SQLException
	 *             SQL 异常
	 */
	@SafeVarargs
	static public void upsert(String table_name, JdbcRecord record,
			List<String>... unique_keys_array) throws SQLException {
		long start = System.currentTimeMillis();
		Connection connection = Jdbc.getConnection();
		connection.setAutoCommit(false);
		PreparedStatement statement = null;
		ResultSet rset = null;
		try {
			boolean updated = false;
			for (List<String> unique_keys : unique_keys_array) {
				statement = prepareQueryStatement(connection, table_name,
						record, unique_keys);
				rset = statement.executeQuery();
				if (!rset.next()) {
					continue;
				}
				ResultSetMetaData metadata = rset.getMetaData();
				for (String field : record.FieldSet()) {
					Object value = record.getField(field);
					if (value instanceof String) {
						value = stringToJdbcObject(metadata, field, (String) value);
					}
					rset.updateObject(field, value);
				}
				logger.debug("查询到存在的记录，更新之...");
				rset.updateRow();
				updated = true;
				break;
			}
			if (!updated && rset != null) {
				rset.moveToInsertRow();
				ResultSetMetaData metadata = rset.getMetaData();
				for (String field : record.FieldSet()) {
					Object value = record.getField(field);
					if (value instanceof String) {
						value = stringToJdbcObject(metadata, field, (String) value);
					}
					rset.updateObject(field, value);
				}
				logger.debug("未查询到存在的记录，插入新记录...");
				rset.insertRow();
			}
			connection.commit();
			Jdbc.logElapsed(start, statement);
		} catch (SQLException ex) {
			connection.rollback();
			throw ex;
		} finally {
			if (rset != null) {
				rset.close();
			}
			if (statement != null) {
				statement.close();
			}
			connection.close();
		}
	}

	/**
	 * 不抛异常版本
	 */
	@SafeVarargs
	static public void upsert_NE(String table_name, JdbcRecord record,
			List<String>... unique_keys_array) {
		try {
			upsert(table_name, record, unique_keys_array);
		} catch (SQLException e) {
			logger.error("插入或更新数据表[{}]数据错误[{}].", table_name, e.getMessage());
		}
	}

	/**
	 * 执行插入或更新操作，根据条件对数据库进行查询，如果记录已经存在，则更新之， 否则插入新记录。
	 *
	 * @param table_name
	 *            数据表名称
	 * @param record
	 *            数据表记录
	 * @param unique_key
	 *            唯一性条件字段
	 * @throws SQLException
	 *             SQL 异常
	 */
	static public void upsert(String table_name, JdbcRecord record,
			String unique_key) throws SQLException {
		List<String> unique_keys = new ArrayList<>();
		if (unique_key != null) {
			unique_keys.add(unique_key);
		}
		upsert(table_name, record, unique_keys);
	}

	/**
	 * 不抛异常版本
	 */
	static public void upsert_NE(String table_name, JdbcRecord record,
			String unique_key) {
		try {
			upsert(table_name, record, unique_key);
		} catch (SQLException e) {
			logger.error("插入或更新数据表[{}]数据错误[{}].", table_name, e.getMessage());
		}
	}

	/**
	 * 从输入流中批量导入数据
	 *
	 * @param table_name
	 *            数据表名
	 * @param stream
	 *            输入流
	 * @param delim
	 *            分隔符
	 * @param encoding
	 *            输入流编码
	 * @throws SQLException
	 *             SQL 异常
	 * @throws IOException
	 *             IO 异常
	 */
	public static void copyIn(String table_name, InputStream stream,
			String delim, String encoding)
			throws SQLException, IOException {
		if (delim == null) {
			delim = ",";
		}
		if (encoding == null) {
			encoding = "UTF-8";
		}
		if (Jdbc.getDBDriver() == Jdbc.DBDRI_PGSQL) {
			Connection connection = Jdbc.getConnection();
			try {
				BaseConnection base_conn =
						(BaseConnection) connection.getMetaData().getConnection();
				CopyManager copier = new CopyManager(base_conn);
				StringBuilder builder = new StringBuilder();
				builder.append("copy ");
				builder.append(table_name);
				builder.append(" from STDIN (delimiter ");
				builder.append("'").append(delim.charAt(0)).append("'");
				builder.append(", encoding ");
				builder.append("'").append(encoding).append("')");
				String sql = builder.toString();
				logger.trace("开始执行SQL[{}]", sql);
				long lines = copier.copyIn(sql, stream);
				logger.info("导入[{}]条记录到数据表[{}].", lines, table_name);
			} catch (Exception e) {
				throw e;
			} finally {
				connection.close();
			}
			return;
		}
		logger.warn("数据库驱动[{}]不支持 copy 操作.", Jdbc.getDBDriver());
	}

	/**
	 * 将数据表导出到输出流中
	 *
	 * @param table_name
	 *            数据表名
	 * @param stream
	 *            输出流
	 * @param delim
	 *            分隔符
	 * @param encoding
	 *            输出流编码
	 * @throws SQLException
	 *             SQL 异常
	 * @throws IOException
	 *             IO 异常
	 */
	public static void copyOut(String table_name, OutputStream stream,
			String delim, String encoding)
			throws SQLException, IOException {
		if (delim == null) {
			delim = ",";
		}
		if (encoding == null) {
			encoding = "UTF-8";
		}
		if (Jdbc.getDBDriver() == Jdbc.DBDRI_PGSQL) {
			Connection connection = Jdbc.getConnection();
			try {
				BaseConnection base_conn =
						(BaseConnection) connection.getMetaData().getConnection();
				CopyManager copier = new CopyManager(base_conn);
				StringBuilder builder = new StringBuilder();
				builder.append("copy ");
				builder.append(table_name);
				builder.append(" to STDOUT (delimiter ");
				builder.append("'").append(delim.charAt(0)).append("'");
				builder.append(", encoding ");
				builder.append("'").append(encoding).append("'");
				builder.append(", null '')");
				String sql = builder.toString();
				logger.trace("开始执行SQL[{}]", sql);
				long lines = copier.copyOut(sql, stream);
				logger.info("从数据表[{}]导出[{}]条记录.", table_name, lines);
			} catch (Exception e) {
				throw e;
			} finally {
				connection.close();
			}
			return;
		}
		logger.warn("数据库驱动[{}]不支持 copy 操作.", Jdbc.getDBDriver());
	}

}
